跳到主要内容

第五章:物理世界:引入物理引擎

  在游戏开发中,物理引擎是让游戏世界变得真实和互动的关键工具。Dora 提供了强大的物理引擎支持,让我们能够轻松实现碰撞检测、重力效果和物体的移动等功能。本章将带您了解如何在游戏中引入物理世界,并创建和管理物理实体。


1. 什么是物理世界?

  物理世界是一个虚拟的环境,它模拟现实中的物理规律,如物体之间的碰撞、速度和加速度。在 Dora 中,物理世界由 PhysicsWorld 节点表示。我们可以通过它来定义物体的行为和交互。


2. 创建物理世界

  在 Dora 中,我们可以通过在场景中添加一个 PhysicsWorld 节点来创建物理世界。

示例代码
import { PhysicsWorld } from 'Dora';
// 创建物理世界节点
const physicsWorld = toNode(<physics-world showDebug/>) as PhysicsWorld.Type;
if (!physicsWorld) error('创建物理世界失败!');

  添加到场景中的 PhysicsWorld 会成为整个物理系统的基础,所有的物理对象都会在它的管理下进行交互。


3. 添加物理实体

  物理实体是能够在物理世界中移动或产生交互的对象。Dora 提供了多种物理实体类型,例如静态(Static)、动态(Dynamic)和运动学(Kinematic)。在本教程中,我们以动态实体为例,创建一个玩家角色的物理实体。

示例代码
import { BodyMoveType } from 'Dora';

// 创建动态物理实体
const player = toNode(
<body
world={physicsWorld}
group={1}
type={BodyMoveType.Kinematic}
linearAcceleration={Vec2.zero}>
<disk-fixture radius={40}/>
</body>
);
if (!player) error('创建玩家失败!');

// 将实体添加到物理世界
player.addTo(physicsWorld);

代码解释

  • type={BodyMoveType.Kinematic}:指定玩家物理实体为运动体类型,即只可以通过代码控制其运动。
  • linearAcceleration={Vec2.zero}:设置物体的线性加速度为 0,即物体不受重力的影响。
  • <disk-fixture radius={40}/>:为物理实体定义一个圆形碰撞区域,半径为 40。

4. 处理物体的碰撞

  通过为物理世界定义碰撞规则,我们可以决定哪些物体可以碰撞以及如何处理碰撞事件。

示例代码
// 定义碰撞规则:组 0 和组 1 之间的碰撞有效
<physics-world>
<contact groupA={0} groupB={1} enabled/>
</physics-world>
// 处理碰撞事件
player.onContactStart(other => {
if (other.group === 0) {
print('玩家与敌人发生碰撞!');
}
});

代码解释

  • <contact groupA={0} groupB={1} enabled/>:定义了组 0 和组 1 之间的碰撞规则。
  • onContactStart:为玩家物理实体添加一个事件,当与其他物体发生碰撞时触发。

5. 动态生成物体

  动态生成物体是实现挑战和互动的重要方式。以下是一个生成敌人的示例:

示例代码
const createEnemy = () => {
const enemy = toNode(
<body
world={physicsWorld}
group={0}
type={BodyMoveType.Dynamic}
linearAcceleration={Vec2.zero}
velocityX={100}
velocityY={100}>
<disk-fixture radius={40}/>
</body>
);
enemy?.addTo(physicsWorld);
};

  通过在游戏循环中调用 createEnemy 方法,我们可以不断生成新的敌人。


6. 控制物体运动

  物理实体的运动可以通过设置速度、施加力或者直接修改位置来实现。以下是一个简单的示例,展示了如何通过键盘输入控制玩家角色的移动。

示例代码
inputManager.pushContext("Game");

let velocityX = 0;
let velocityY = 0;

player.gslot('Input.Up', () => velocityY = 1);
player.gslot('Input.Down', () => velocityY = -1);
player.gslot('Input.Left', () => velocityX = -1);
player.gslot('Input.Right', () => velocityX = 1);

player.loop(() => {
const newPos = player.position.add(Vec2(velocityX, velocityY).normalize().mul(10));
player.position = newPos;
velocityX = 0;
velocityY = 0;
return false;
});

代码解释

  • velocityXvelocityY:用于存储玩家角色的速度。
  • player.gslot:为玩家角色绑定键盘输入事件。
  • player.loop:在游戏循环中更新玩家角色的位置。

7. 综合示例:在物理世界实现敌人生成和玩家控制

  以下代码结合上面的示例,实现了一个简单的游戏场景,其中玩家可以控制角色移动,同时每秒生成一个敌人。

示例代码
import { BodyMoveType, PhysicsWorld } from 'Dora';

// 创建敌人的方法
const Enemy = (world: PhysicsWorld.Type) => {
const enemy = toNode(
<body
world={world}
group={0}
type={BodyMoveType.Dynamic}
linearAcceleration={Vec2.zero}
velocityX={math.random(-100, 100)}
velocityY={math.random(-100, 100)}>
<disk-fixture radius={40}/>
</body>
)?.addTo(world);
if (!enemy) error('创建敌人失败!');

// 为敌人添加动画
const enemyAnim = Node().addTo(enemy);
const animations = ['enemyFlyingAlt_', 'enemyWalking_', 'enemySwimming_'];
playAnimation(enemyAnim, animations[math.random(0, animations.length - 1)]);
};

// 创建玩家的方法
const Player = (world: PhysicsWorld.Type) => {
const player = toNode(
<body
world={world}
group={1}
type={BodyMoveType.Kinematic}
linearAcceleration={Vec2.zero}>
<disk-fixture radius={40}/>
</body>
)?.addTo(world);
if (!player) error('创建玩家失败!');

// 为玩家添加动画
const playerAnim = Node().addTo(player);
playAnimation(playerAnim, "playerGrey_walk");

// 玩家移动控制
let velocityX = 0;
let velocityY = 0;

player.gslot('Input.Up', () => velocityY = 1);
player.gslot('Input.Down', () => velocityY = -1);
player.gslot('Input.Left', () => velocityX = -1);
player.gslot('Input.Right', () => velocityX = 1);

player.loop(() => {
const newPos = player.position.add(Vec2(velocityX, velocityY).normalize().mul(10));
player.position = newPos;
velocityX = 0;
velocityY = 0;
return false;
});
}

// 创建游戏场景的组件
const Game = () => {
// 切换到 Game 输入上下文,响应玩家操作
inputManager.pushContext("Game");

// 创建物理世界
return (
<physics-world onMount={world => {
Player(world); // 创建玩家

// 游戏循环:每 1 秒生成一个敌人
world.loop(() => {
sleep(1);
Enemy(world); // 创建敌人
return false;
});
}}>
<contact groupA={0} groupB={1} enabled/> {/* 敌人与玩家碰撞 */}
<contact groupA={0} groupB={0} enabled={false}/> {/* 敌人之间碰撞无效 */}
</physics-world>
);
};

// 注册开始场景发出的游戏开始事件
Director.entry.gslot("Input.Start", () => {
Director.entry.removeAllChildren();
// 进入游戏场景
toNode(<Game/>);
});

8. 小结

  通过本章内容,我们学习了如何:

  1. 创建物理世界并将其添加到场景中。
  2. 创建物理实体并定义碰撞规则。
  3. 动态生成物体并控制它们的运动。

  在接下来的章节中,我们将继续完善游戏,加入更多功能和优化,让您的游戏变得更加丰富和有趣!